﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Text;
using System.Windows.Input;
using System.Reactive.Concurrency;

namespace MVVMCommon.ViewModels.Commands
{
	public class ReactiveCommand : IObserver<bool>, IObservable<object>, ICommand, IDisposable
	{
		public ReactiveCommand(bool canExecuteFirstValue, Predicate<object> canExecutePredicate = null)
		{
			disposables = new CompositeDisposable();

			disposables.Add(_canExecuteSubject = new BehaviorSubject<bool>(canExecuteFirstValue));

			disposables.Add(
					_canExecuteSubject
						.SubscribeOn(System.Threading.SynchronizationContext.Current)
						.Subscribe(
							v =>
							{
								_canExecute = v;
								CanExecuteChanged(this, EventArgs.Empty);
							})
				);
			
			disposables.Add(_executeSubject = new Subject<object>());

			_canExecutePredicate = canExecutePredicate;
		}

		Predicate<object> _canExecutePredicate;
		bool _canExecute;

		CompositeDisposable disposables;

		BehaviorSubject<bool> _canExecuteSubject;
		Subject<object> _executeSubject;

		public void OnCompleted()
		{
			_canExecuteSubject.OnCompleted();
		}

		public void OnError(Exception error)
		{
			_canExecuteSubject.OnError(error);
		}

		public void OnNext(bool value)
		{
			_canExecuteSubject.OnNext(value);
		}

		public IDisposable Subscribe(IObserver<object> observer)
		{
			return _executeSubject.Subscribe(observer);
		}

		public bool CanExecute(object parameter)
		{
			if (_canExecutePredicate!=null)
			{
				var rval = _canExecutePredicate(parameter);
				_canExecuteSubject.OnNext(rval);
				_canExecute = rval;
  
			}

			return _canExecute;
		}

		public event EventHandler CanExecuteChanged = (s, e) => { };

		public void Execute(object parameter)
		{
			if (CanExecute(parameter))
			{
				_executeSubject.OnNext(parameter);
			}
		  
		}

		public void Dispose()
		{
			disposables.Dispose();
		}
	}
}
